/**
 * 本章内容
 *  1.理解迭代
 *  2.迭代器模式
 *  3.生成器
 * 7.1理解迭代
 * 7.2迭代器模式
 *  任何实现 Iterable 接口的数据结构都可以被实现 Iterator 接口的结构“消费”（consume）。迭
    代器（iterator）是按需创建的一次性对象。每个迭代器都会关联一个可迭代对象，而迭代器会暴露迭代
    其关联可迭代对象的 API。迭代器无须了解与其关联的可迭代对象的结构，只需要知道如何取得连续的
    值。这种概念上的分离正是 Iterable 和 Iterator 的强大之处。
    7.2.1 可迭代协议
        1、实现Iterable接口(可迭代协议)要求具备两种能力：支持迭代的自我识别能力和创建实现Iterator接口的对象的能力。
            在ES中，这意味着必须暴露一个属性作为默认迭代器，而且这个属性必须使用特殊的Symbol.iterator作为键。
            这个默认迭代器属性必须引用一个迭代器工厂函数，调用这个工厂函数必须返回一个新迭代器。
            很多内置类型都实现了Iterable接口：
                字符串
                数组
                映射
                集合
                arguments对象
                NodeList等DOM集合类型
        2、实际代码过程中，不需要显示调用这个工厂函数来生成迭代器。
            for of 循环
            数组解构
            扩展操作符
            Array.from()
            创建集合
            创建映射
            Promise.all()接收由期约组成的可迭代对象
            Promise.race()接收由期约组成的可迭代对象
            yield* 操作符在生成器中使用
            这些原生语言结构会在后台调用提供的可迭代对象的这个工厂函数，从而创建一个迭代器。
 * 7.2.2 迭代器协议
        迭代器是一种一次性使用的对象，用于迭代与其关联的可迭代对象。
        迭代器API使用next（）方法在可迭代对象中遍历数据，每次成功调用next（），都会返回一个IneratorResult对象，其中包含迭代器返回的下一个值。若不调用next（），则无法知道迭代器的当前位置。
        next()方法返回的迭代器对象IteratorResult包含两个属性：done和value。done是个布尔值，表示是否可以调用next()取得下一个值。
        value包含可迭代对象的下一个值（done是false），或者undefined（done是true），true的状态是耗尽。
        每个迭代器都表示对可迭代对象的一次性有序遍历。不同迭代器的实例互相之间没有联系，只会独立地遍历可迭代对象。
        注意：迭代器并不与可迭代对象某个时刻的快照绑定，而仅仅是使用游标来记录可迭代对象的历程。如果可迭代对象在迭代期间被修改了，那么迭代器也会相应的变化。
        迭代器维护着一个指向可迭代对象的引用，因此迭代器会阻止垃圾回收程序回收可迭代对象。
   7.2.3 自定义迭代器
   7.2.4 提前终止迭代器
        用return()提前关闭执行的迭代器逻辑
        for-of循环通过break、continue、return 或者 throw提前退出
 * 7.3 生成器  拥有在一个函数块内暂停和恢复代码执行的能力。
 *   7.3.1 生成器基础
 *      生成器是一个函数，函数名称前面加一个星号（*）表示它是一个生成器。只要是可以定义函数的地方，就可以定义生成器。
 *      注意：箭头函数不能用来定义生成器函数,标识生成器函数的星号不受两侧空格的影响。
 *      调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行的状态。与迭代器相似，生成器对象实现了Iterator接口，因此具有next()方法。
 *      next()方法的返回值类似于迭代器，有一个 done 属性和一个 value 属性。函数体为空的生成器函数中间不会停留，调用一次 next()就会让生成器到达 done: true 状态。
 *      value 属性是生成器函数的返回值，默认值为 undefined，可以通过生成器函数的返回值指定：
 *      生成器函数只会在初次调用 next()方法后开始执行
 *      生成器对象实现了 Iterable 接口，它们默认的迭代器是自引用的
 *   7.3.2 通过yield中断执行
 *      yield关键字可以让生成器停止和开始执行。生成器在遇到yield关键字之前会正常执行。遇到这个关键字之后执行会停止函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用next()方法来恢复执行。
 *      生成器函数内部的执行流程会针对每个生成器对象区分作用域。在一个生成器对象上调用 next()不会影响其他生成器
 *      yield 关键字只能在生成器函数内部使用，用在其他地方会抛出错误。类似函数的 return 关键字，yield 关键字必须直接位于生成器函数定义中，出现在嵌套的非生成器函数中会抛出语法错误
 *      1. 生成器对象作为可迭代对象 
 *      在生成器对象上显式调用 next()方法的用处并不大。其实，如果把生成器对象当成可迭代对象，那么使用起来会更方便
 *      2.使用yield实现输入和输出
 *      3.产生可迭代对象
 *         可以使用*号增强yield的行为，让它能够迭代一个可迭代的对象，从而一次产生一个值。
 *      4.使用yield*实现递归算法
 *      yield*最有用的地方是实现递归操作
 *    7.3.3 生成器作为默认迭代器
 *      创建一个类 然后命名一个生成器函数 *[Symbol.iterator]() { yield* this.values}
 *    7.3.4 提前终止生成器
 *      1.return()会强制生成器进入关闭状态。
 *          与迭代器不同，所有生成器对象都有 return()方法，只要通过它进入关闭状态，就无法恢复了。
 *      2.throw() 
 *          throw()方法会在暂停的时候将一个提供的错误注入到生成器对象中。如果错误未被处理，生成器就会关闭
 *          不过，假如生成器函数内部处理了这个错误，那么生成器就不会关闭，而且还可以恢复执行。
 *  
 */
// 检查是否实现迭代器工厂函数()
let num = 1;
console.log(num[Symbol.iterator])
let str = '123'
// 实现了迭代工厂函数
console.log(str[Symbol.iterator])
// 调用迭代工厂函数会生成一个迭代器
console.log(str[Symbol.iterator]())

let arr = ['foo', 'bar', 'baz']
for (let el of arr) { 
    console.log(el)
}
// 数组解构
let [a, b, c] = arr
console.log(a, b, c)
// 扩展操作符
let arr2 = [...arr]
// Array.form()
let arr3 = Array.from(arr)
console.log(arr3)
// Set构造函数
let set = new Set(arr)
console.log(set)
// Map构造函数
let pairs = arr.map((x, i) => [x, i])
let map = new Map(pairs)
console.log(map)
// 可迭代对象
let arrr = ['foo', 'bar']
// 迭代器工厂函数
console.log(arr[Symbol.iterator])
// 迭代器
let iter = arr[Symbol.iterator]()
// 执行迭代
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())

// 生成器
// 生成器函数声明 
function* generatorFn() {
    return 'foo'
 }
// 生成器函数表达式
const g = generatorFn()
console.log(g.next())
console.log(g[Symbol.iterator]())

// 生成器通过yield中断执行
function* generatorFnYield() {
    yield
}
const generatorObject = generatorFnYield()
console.log(generatorObject.next())
console.log(generatorObject.next())

// 使用yield输入和输出
function* generatorFnInTo(initial) { 
    console.log(initial,'1')
    console.log(yield)
    console.log(yield)
}
let generatorFnInTO = generatorFnInTo('foo')
console.log(generatorFnInTO.next('bars'),'输入')
console.log(generatorFnInTO.next('baz'),'输入')
console.log(generatorFnInTO.next('qux'),'输入')

function* generatorFnTo() { 
    return yield 'foo12'
}
let generatorFnToo = generatorFnTo();
console.log(generatorFnToo.next())
console.log(generatorFnToo.next('bar'))

// 产生可迭代对象
function* generatorFnObject() { 
    yield* [1, 2, 3]
    yield* [3, 4]; 
    yield * [5, 6]; 
}
const fnObject = generatorFnObject()
console.log(fnObject.next())
for (const x of fnObject) { 
    console.log(x,'对象')
}

// 使用yield递归
function* nTimes(n) { 
    if (n>0) { 
        yield* nTimes(n - 1)
        yield n -1 
    }
}

for (const x of nTimes(3)) { 
    console.log(x)
}